/*
 * $Id: BoundingBox.java,v 1.8 2002/02/05 19:25:35 ezb Exp $
 *
 * $Date: 2002/02/05 19:25:35 $
 *
 * Copyright (c) Eric Z. Beard, ericzbeard@hotmail.com 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */
package gnu.jpdf;

import java.awt.*;
import java.util.*;

/**
 * <p>This class simplifies the placement of Strings within 
 * a canvas area where the placement of objects is absolute</p>
 *
 * <p>A <code>BoundingBox</code> is just a Rectangle that knows how to 
 * find the coordinates for a String based on the desired alignment and 
 * <code>FontMetrics</code>.  For each new String, a new child 
 * <code>BoundingBox</code> is made that can be subtracted from the 
 * original box so new Strings can be added</p>
 *
 * <p>One of the more helpful features of this class is the string wrap 
 * feature of <code>getStringBounds</code>.  The box returned by that method 
 * will contain an array of strings that have been broken down to fit the 
 * box.  The box's coordinates and size will reflect the size of the 
 * entire group of strings if it is laid out as expected.  Using the 
 * returned box and iterating through the array of strings from top to 
 * bottom, getting new bounding boxes for each one (with upper left 
 * alignment and no padding) will result in the correct string wrap.</p>
 *
 * <p>Note that you will need to have Xvfb running on a Unix server to 
 * use this class</p>
 *
 * @author Eric Z. Beard, ericzbeard@hotmail.com
 * @author $Author: ezb $
 * @version $Revision: 1.8 $, $Date: 2002/02/05 19:25:35 $
 */
public class BoundingBox extends Rectangle
{
	private static final long serialVersionUID = 1L;

	/** Percent f line height to space lines */
	public static final int LINE_SPACING_PERCENTAGE = 20;

	/** Used to a align a String centered vertically */
	public static final int VERT_ALIGN_CENTER  = 0;

	/** Used to align a String at the top of the box */
	public static final int VERT_ALIGN_TOP     = 1;

	/** Used to align a String at the bottom of the box */
	public static final int VERT_ALIGN_BOTTOM  = 2;

	/** Used to align a String horizontally in the center of the box */
	public static final int HORIZ_ALIGN_CENTER = 3;

	/** Used to align a String to the left in the box */
	public static final int HORIZ_ALIGN_LEFT   = 4;

	/** Used to aling a String to the right in a box */
	public static final int HORIZ_ALIGN_RIGHT  = 5;

	/** Used to subtract a child from a box, *leaving* the top portion */
	public static final int SUBTRACT_FROM_TOP    = 6;

	/** Used to subtract a child from a box, *leaving* the bottom portion */
	public static final int SUBTRACT_FROM_BOTTOM = 7;

	/** Used to subtract a child from a box, *leaving* the left portion */
	public static final int SUBTRACT_FROM_LEFT   = 8;

	/** Used to subtract a child from a box, *leaving" the right portion */
	public static final int SUBTRACT_FROM_RIGHT  = 9;

	private static final int[] VERT_ALIGNS = {VERT_ALIGN_CENTER,
		VERT_ALIGN_TOP,
		VERT_ALIGN_BOTTOM};

	private static final int[] HORIZ_ALIGNS = {HORIZ_ALIGN_CENTER,
		HORIZ_ALIGN_LEFT,
		HORIZ_ALIGN_RIGHT};

	private static final int[] SUBTRACTS = {SUBTRACT_FROM_TOP,
		SUBTRACT_FROM_BOTTOM,
		SUBTRACT_FROM_LEFT,
		SUBTRACT_FROM_RIGHT};

	/** The point to use for Graphics.drawString() */
	private Point drawingPoint;

	/** The absolute, world location of the box */
	private Point absoluteLocation;

	/** Link to parent box */
	private BoundingBox parent;

	/** 
	 * If this box was the result of a getStringBounds call, this 
	 * array will hold the broken strings
	 */
	private String[] stringArray;

	/** The string specified in getStringBounds */
	private String fullString;

	/**
	 * Creates a new <code>BoundingBox</code> instance.
	 *
	 * @param p a <code>Point</code>, upper left coords
	 * @param d a <code>Dimension</code>, used to determine height and width
	 */
	public BoundingBox(Point p, Dimension d) 
	{
		super(p, d);
		this.drawingPoint = this.getLocation();
		this.absoluteLocation = this.getLocation();
	}

	/**
	 * <p>Returns true if this box has a parent.  The 'world', or 
	 * enclosing canvas is not considered a parent</p>
	 *
	 * @return a <code>boolean</code> value
	 */
	public boolean hasParent() 
	{
		if (parent == null)
			return false;
		else 
			return true;
	}

	/**
	 * <p>Get this box's parent box</p>
	 *
	 * @return a <code>BoundingBox</code> value
	 */
	public BoundingBox getParent() 
	{return parent;}

	/**
	 * <p>Make the specified box this box's child.  Equivalent to 
	 * <code>child.setParent(parent)</code> where the specified 'parent' is 
	 * this instance</p>
	 *
	 * @param child a <code>BoundingBox</code>, any box that can fit inside 
	 *              this one.  The results of calling 
	 *              <code>getAbsoluteLocation()</code> on the child will be 
	 *              altered after this to take into account the child's 
	 *              new location in the 'world'
	 *
	 */
	public void add(BoundingBox child) 
	{child.setParent(this);}

	/**
	 * <p>Make the specified box this box's parent</p>
	 *
	 * @param parent a <code>BoundingBox</code> value
	 */
	public void setParent(BoundingBox parent)
	{
		// Prevent infinite recursion
		if (this == parent)
			return;
		this.parent = parent;
		// If this box was created empty, without a String inside,
		// determine its absolute location
		if (this.getLocation().equals(this.getAbsoluteLocation())) 
		{
			int ancestorTranslateX = 0;
			int ancestorTranslateY = 0;

			BoundingBox ancestor = this;
			while (ancestor.hasParent()) 
			{
				BoundingBox oldRef = ancestor;
				ancestor = ancestor.getParent();
				// Prevent infinite recursion
				if (ancestor == oldRef)
					break;
				ancestorTranslateX += (int)ancestor.getLocation().getX();
				ancestorTranslateY += (int)ancestor.getLocation().getY();
			}
			this.getAbsoluteLocation().translate(ancestorTranslateX, ancestorTranslateY);
		} // end if
	} // end setParent

	/**
	 * <p>Get the wrapped strings if this box was from a call to getStringBounds,
	 * otherwise this method returns null</p>
	 *
	 * @return a <code>String[]</code> array of strings, top to bottom in layout
	 */
	public String[] getStringArray() 
	{return stringArray;} // end getStringArray

	/**
	 * <p>Set the value of the string array</p>
	 *
	 * @param strArray  a <code>String</code> array
	 * 
	 */
	public void setStringArray(String[] strArray) 
	{this.stringArray = strArray;}

	/**
	 * <p>Set the absolute upper left world location point for this box</p>
	 *
	 * @param point a <code>Point</code> value
	 */
	public void setAbsoluteLocation(Point point) 
	{this.absoluteLocation = point;}

	/** 
	 * <p>Returns false if for any reason this box has negative dimensions</p>
	 */
	public boolean boxExists() 
	{
		if ((this.getHeight() < 0) || (this.getWidth() < 0))
			return false;
		return true;
	} // end boxExists

	/**
	 * <p>Get the absolute upper left location point for this box</p>
	 *
	 * @return a <code>Point</code> value
	 */
	public Point getAbsoluteLocation() 
	{return absoluteLocation;}

	/**
	 * <p>Returns the full string associated with a call to 
	 *    <code>getStringBounds</code></p>
	 */
	public String getFullString() 
	{return fullString;}

	/**
	 * <p>Sets the full string associated with <code>getStringBounds</code></p>
	 *
	 * @param string a <code>String</code>
	 */
	public void setFullString(String string) 
	{this.fullString = string;}

	/**
	 * <p>Gets the location of a String after it is adjusted for 
	 * alignment within this box.  The point's coordinates are 
	 * either within this box or within the enclosing area.</p>
	 *
	 * @param string a <code>String</code>, the String to be placed
	 * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, 
	 *        HORIZ_ALIGN_LEFT, HORIX_ALIGN_RIGHT         
	 * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, 
	 *        VERT_ALIGN_TOP, VERT_ALIGN_BOTTOM
	 * @param fm a <code>FontMetrics</code> object for this String
	 * @param padding an <code>int</code>, the padding around the String
	 * @param enforce a <code>boolean</code>, if true the method will throw 
	 *        an exception when the string is too big, if not true it will break 
	 *        the string down and overrun the bottom of the box.  If the box 
	 *        is too small for even one word, the exception will be thrown
	 * @return a <code>Point</code>, the coords to use in drawString()
	 * @see #HORIZ_ALIGN_LEFT
	 * @see #HORIZ_ALIGN_CENTER
	 * @see #HORIZ_ALIGN_RIGHT
	 * @see #VERT_ALIGN_TOP
	 * @see #VERT_ALIGN_CENTER
	 * @see #VERT_ALIGN_BOTTOM
	 * @throws <code>IllegalArgumentException</code> if the args are invalid
	 * @throws <code>StringTooLongException</code> if the string won't fit 
	 *         and enforce is set to true.  The exception can still be thrown 
	 *         if enforce is false, but only in cases such as the box having 
	 *         no height or width
	 */
	public BoundingBox getStringBounds(String string, int hAlign, int vAlign, 
			FontMetrics fm,int padding,boolean enforce)
	throws IllegalArgumentException, StringTooLongException 
	{                                    
		// Check to make sure the values passed in are valid
		if (!checkHAlign(hAlign))
			throw new IllegalArgumentException("BoundingBox.getStringBounds, " + 
				"hAlign invalid : " + hAlign);
		if (!checkVAlign(vAlign))
			throw new IllegalArgumentException("BoundingBox.getStringBounds, " + 
					"vAlign invalid : " + hAlign);
		if (fm == null)
			throw new IllegalArgumentException("BoundingBox.getStringBounds, " + 
			"FontMetrics null");
		if (string == null)
			throw new IllegalArgumentException("BoundingBox.getStringBounds, " + 
			"String null");

		// NOTE: For this portion of the method, parent refers 
		// to this object and child refers to the object about 
		// to be created.  When the absolute point for drawing the 
		// String is determined, this object's ancestors are checked.
		Point parentLocation = this.getLocation();
		Dimension parentSize = this.getSize();

		Point childLocation;
		Dimension childSize;

		// String ascent, width, height, parent, child width, height
		int sa, sw, sh, pw, ph, cw, ch;

		// Child, parent x, y coords for upper left
		int cx, cy, px, py;

		sa = fm.getMaxAscent();
		sw = fm.stringWidth(string);
		sh = sa + fm.getMaxDescent();
		pw = (int)parentSize.getWidth();
		ph = (int)parentSize.getHeight();
		if (pw < 0)
			throw new StringTooLongException("The parent box has a negative width " + 
					" (" + pw + ")");
		if (ph < 0)
			throw new StringTooLongException("The parent box has a negative height"+
					" (" + ph + ")");
		cw = sw + padding*2;
		ch = sh + padding*2;
		px = (int)this.getX();
		py = (int)this.getY();

		String[] childStrArray = null;

		if ((cw > pw) || (string.indexOf("\n") != -1)) 
		{
			cw = pw - (padding * 2);
			childStrArray = createStringArray(string, fm, padding, pw);
			ch = getWrappedHeight(childStrArray, fm, padding);
			if (ch > ph)
				// If enforce is not true, it means we want the box to 
				// be returned anyway (along with the strings in the array)
				// so we can chop them manually and try again
				if (enforce)
					throw new StringTooLongException("The wrapped strings do not " + 
							"fit into the parent box, pw=" + pw + 
							", ph=" + ph + ", ch=" + ch + ", cw=" + cw + 
							", string: " + string);
		}

		// Need to have child width and height, and string array set

		// Child location is relative to this (parent) box, not the world
		if (vAlign == VERT_ALIGN_TOP)
			cy = 0;      
		else if (vAlign == VERT_ALIGN_CENTER)
			cy = (ph/2) - (ch/2);
		else
			cy = ph - ch;
		if (hAlign == HORIZ_ALIGN_LEFT)
			cx = 0;
		else if (hAlign == HORIZ_ALIGN_CENTER)
			cx = (pw/2) - (cw/2);
		else
			cx = pw - cw;

		childLocation = new Point(cx, cy);
		childSize = new Dimension(cw, ch);

		// Drawing location is based on the baseline of the String, and 
		// relative to the world, not this box.  The drawing point differs 
		// from the absolute box location because of padding and ascent
		int dpx, dpy, abx, aby;

		// If this object also has a parent (maybe grandparents), iterate 
		// through them and find the absolute 'world' location
		int ancestorTranslateX = 0;
		int ancestorTranslateY = 0;

		BoundingBox ancestor = this;
		while (ancestor.hasParent()) 
		{
			BoundingBox oldRef = ancestor;
			ancestor = ancestor.getParent();
			// Prevent infinite recursion
			if (ancestor == oldRef)
				break;
			ancestorTranslateX += (int)ancestor.getLocation().getX();
			ancestorTranslateY += (int)ancestor.getLocation().getY();
		}

		// Determine the absolute location for the box
		abx = px + cx + ancestorTranslateX;
		aby = py + cy + ancestorTranslateY;

		// Determine the absolute drawing point for the String
		dpx = abx + padding;
		dpy = aby + padding + sa;

		Point drawingPoint = new Point(dpx, dpy);
		BoundingBox returnChild = new BoundingBox(childLocation, childSize, 
				drawingPoint, new Point(abx, aby));
		this.add(returnChild);
		returnChild.setFullString(string);
		returnChild.setStringArray(childStrArray);
		return returnChild;
	} // end getStringBounds

	/**
	 * <p>Gets the location of a String after it is adjusted for 
	 * alignment within this box.  The point's coordinates are 
	 * either within this box or within the enclosing area.</p>
	 *
	 * <p>By default, this method enforces string length and throws the 
	 * exception if it is too long</p>
	 *
	 * @param string a <code>String</code>, the String to be placed
	 * @param hAlign an <code>int</code>, HORIZ_ALIGN_CENTER, 
	 *        HORIZ_ALIGN_LEFT, HORIX_ALIGN_RIGHT         
	 * @param vAlign an <code>int</code>, VERT_ALIGN_CENTER, 
	 *        VERT_ALIGN_TOP, VERT_ALIGN_BOTTOM
	 * @param fm a <code>FontMetrics</code> object for this String
	 * @param padding an <code>int</code>, the padding around the String
	 * @return a <code>Point</code>, the coords to use in drawString()
	 * @throws <code>IllegalArgumentException</code> if the args are invalid
	 * @throws <code>StringTooLongException</code> if the string won't fit
	 */
	public BoundingBox getStringBounds(String string, int hAlign, int vAlign, 
			FontMetrics fm,int padding) 
	throws StringTooLongException, IllegalArgumentException 
	{return getStringBounds(string, hAlign, vAlign, fm, padding, true);
	} // end getStringBounds (enforce true by default)

	/**
	 * <p>This method is called after getting the box by calling 
	 * <code>getStringBounds</code> on the parent.  Wraps the string at 
	 * word boundaries and draws it to the specified <code>Graphics</code>
	 * context.  Make sure padding is the same as specified for the 
	 * <code>getStringBounds</code> call, or you may get an unexpected 
	 * {@link gnu.jpdf.StringTooLongException}</p>
	 *
	 * @param g the <code>Graphics</code> object
	 * @param fm the <code>FontMetrics</code> to use for sizing
	 * @param padding an int, the padding around the strings
	 * @param hAlign the <code>int</code> horizontal alignment
	 * @throws <code>IllegalArgumentException</code> if the args are invalid
	 * @throws <code>StringTooLongException</code> if the string 
	 *         won't fit this will only happen if the fm or padding has 
	 *         been changed since getStringBounds was called succesfully
	 */
	public void drawWrappedString(Graphics    g, FontMetrics fm,int         padding,
			int         hAlign)throws IllegalArgumentException, StringTooLongException 
	{
		if (getStringArray() == null) 
		{
			Point p = getDrawingPoint();
			int xx = (int)p.getX();
			int yy = (int)p.getY();
			g.drawString(getFullString(), xx, yy);
		}
		else 
		{
			int len = stringArray.length;
			for (int i = 0; i < len; i++) 
			{
				BoundingBox wrappedBox = null;
				wrappedBox = getStringBounds(stringArray[i],hAlign, 
						BoundingBox.VERT_ALIGN_TOP, fm,0);
				Point pp = wrappedBox.getDrawingPoint();
				int xx = (int)pp.getX();
				if (hAlign == BoundingBox.HORIZ_ALIGN_RIGHT)
					xx -= padding;
				if (hAlign == BoundingBox.HORIZ_ALIGN_LEFT)
					xx += padding;
				int yy = (int)pp.getY() + padding;
				g.drawString(stringArray[i], xx, yy);
				subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
			}
		}
	} // end drawWrappedString

	/**
	 * <p>Draws lines from the wrapped string until there is no more room and
	 * then stops.  If there is no string or the box is too small for 
	 * anything to be drawn, does nothing</p>
	 *
	 * @param g the <code>Graphics</code> object to draw to
	 * @param fm the <code>FontMetrics</code> object to use for string sizing
	 * @param padding the <code>int</code> amount of padding around the string
	 * @param hAlign the <code>int</code> horizontal alignment
	 * 
	 */
	public void drawWrappedStringTruncate(Graphics g, FontMetrics fm,int padding,
			int hAlign)
	{
		if (getStringArray() == null) 
		{
			Point p = getDrawingPoint();
			int xx = (int)p.getX();
			int yy = (int)p.getY();
			if (getFullString() != null)
				g.drawString(getFullString(), xx, yy);
			else
				System.err.println("getStringArray and getFullString are null");
		}
		else 
		{
			int totalHeight = 0;
			int len = stringArray.length;
			for (int i = 0; i < len; i++) 
			{
				BoundingBox wrappedBox = null;
				try 
				{
					wrappedBox = getStringBounds(stringArray[i],hAlign, 
							BoundingBox.VERT_ALIGN_TOP, fm,0,false);
					totalHeight += (int)wrappedBox.getHeight();
					if (getParent() != null)
						if (totalHeight > (int)(getParent().getHeight()))
							return;
				}
				catch (StringTooLongException stle) 
				{
					stle.printStackTrace();
					return;
				}
				wrappedBox.drawChoppedString(g, fm, padding, hAlign);
				subtract(wrappedBox, BoundingBox.SUBTRACT_FROM_BOTTOM);
			}
		}
	} // end drawWrappedStringTruncate

	/**
	 * <p>Take the first line of the string (if it is wrapped, otherwise just 
	 * take the whole string) and chop the end of it off to make it fit in the 
	 * box.  If the box is smaller than one letter, draw nothing</p>
	 *
	 * @param g the <code>Graphics</code> object to draw to
	 * @param fm the <code>FontMetrics</code> object to use for string sizing
	 * @param padding the <code>int</code> amount of padding around the string
	 * @param hAlign the <code>int</code> horizontal alignment
	 */
	public void drawChoppedString(Graphics g,FontMetrics fm,int padding,int hAlign) 
	{
		String string= "";
		if (getStringArray() != null)
			string = new String(getStringArray()[0]);
		else
			string = new String(getFullString());
		BoundingBox choppedBox = null;
		try 
		{
			choppedBox = getStringBounds(string,hAlign,VERT_ALIGN_TOP,fm,padding);
			Point p = choppedBox.getDrawingPoint();
			int x = (int)p.getX();
			int y = (int)p.getY();
			g.drawString(string, x, y);
		}
		catch (StringTooLongException stle) 
		{
			// Doesn't fit - start cutting from the end until it does
			StringBuffer buf = new StringBuffer().append(string);
			if (buf.length() == 0) 
			{
				System.out.println("BoundingBox.drawChoppedString, buf len 0 ??");
				//return;
				throw new RuntimeException();
			}
			buf.deleteCharAt(buf.length()-1);
			while ((fm.stringWidth(buf.toString()) > (int)getWidth()) && 
					(buf.length() > 0))
				buf.deleteCharAt(buf.length()-1);
			try 
			{
				choppedBox = getStringBounds(buf.toString(),hAlign,
						VERT_ALIGN_TOP,fm,padding);
				Point pp = choppedBox.getDrawingPoint();
				int xx = (int)pp.getX();
				int yy = (int)pp.getY();
				g.drawString(string, xx, yy);
			}
			catch (StringTooLongException sstle) 
			{
				// Must be a really small box!
				sstle.printStackTrace();
			}
		}
	} // end drawChoppedString

	/**
	 * <p>Get the total height of the box needed to contain the strings in 
	 * the specified array</p>
	 */
	private int getWrappedHeight(String[] strings, FontMetrics fm, int padding) 
	{
		int ma = fm.getMaxAscent();
		int md = fm.getMaxDescent();
		int sh = ma + md;
		int hPad = sh / LINE_SPACING_PERCENTAGE;
		sh += hPad;
		int total = sh * strings.length; 
		return total + (padding*2);
	} // end getWrappedHeight

	/**
	 *
	 * <p>Make a string array from a string, wrapped to fit the box</p> 
	 *
	 * <p>If the line width is too short, the array is just a 
	 * tokenized version of the string</p>
	 *
	 * @param string - the <code>String</code> to convert to an array
	 * @param
	 */
	private String[] createStringArray(String      string, 
			FontMetrics fm, int         padding,int         pw) 
	{
		if (string == null) 
		{
			System.err.println("Tried createStringArray with null String");
			return null;
		}
		if (fm == null)
			System.err.println("Tried createStringArray with null FontMetrics");
		int sw = fm.stringWidth(string);
		int lw = pw - (padding*2);

		Vector<String> returnVector = new Vector<String>();
		// Return delimiters as tokens
		StringTokenizer st = new StringTokenizer(string, " \t\n\r\f", true);
		StringBuffer tempBuffer = new StringBuffer();
		StringBuffer finalBuffer = new StringBuffer();
		while(st.hasMoreTokens()) 
		{
			// Get the next word and add a space after it 
			String tempString = st.nextToken();
			tempBuffer.append(tempString);

			// If we haven't reached the width with our current 
			// line, keep adding tokens.  Also, check for hard returns
			if ((fm.stringWidth(tempBuffer.toString()) < lw) && 
					(tempBuffer.toString()
							.charAt(tempBuffer.toString().length() - 1) != '\n') && 
							(tempBuffer.toString()
									.charAt(tempBuffer.toString().length() - 1) != '\r')) {
				finalBuffer.append(tempString);
				continue;
			} 
			else 
			{  
				returnVector.addElement(finalBuffer.toString());
				finalBuffer.delete(0, finalBuffer.length());
				tempBuffer.delete(0, tempBuffer.length());
				if ((tempString.charAt(0) != '\n') && 
						(tempString.charAt(0) != '\r')) 
				{
					tempBuffer.append(tempString);
					finalBuffer.append(tempString);
				}
				continue;
			}          

		} // end while
		returnVector.addElement(finalBuffer.toString());
		int len = returnVector.size();
		// Init the class member field stringArray
		String[] childStrArray = new String[len];
		for (int i = 0; i < len; i++) 
		{
			String curStr = (String)returnVector.get(i);
			childStrArray[i] = curStr; 
		}
		return childStrArray;
	} // end createStringArray 

	/**
	 * <p>Removes the child box from this parent box.  The child must 
	 * have this object as its parent or the method does nothing.  
	 * The BoundingBox returned will be cut by an area equal to 
	 * the child area plus the horizontal or vertical strip in 
	 * which it sits, depending on the 'subtractFrom' value passed
	 * in</p>
	 *
	 * @param child a <code>BoundingBox</code> value
	 * @param int an <code>int</code>, SUBTRACT_FROM_LEFT, 
            SUBTRACT_FROM_RIGHT, SUBTRACT_FROM_TOP,
            SUBTRACT_FROM_BOTTOM
	 * @return a <code>BoundingBox</code> value
	 * @see #SUBTRACT_FROM_LEFT
	 * @see #SUBTRACT_FROM_RIGHT
	 * @see #SUBTRACT_FROM_TOP
	 * @see #SUBTRACT_FROM_BOTTOM
	 */
	public BoundingBox subtract(BoundingBox child, int subtractFrom) 
	{  
		// First, check to see if the params are valid
		if (child == null)
			throw new IllegalArgumentException("BoundingBox.subtract, " + 
			"BoundingBox child is null");
		if (!child.hasParent()) 
		{
			throw new IllegalArgumentException("BoundingBox.subtract, " + 
			"BoundingBox child has no parent");
		}
		else 
		{
			if (!(child.getParent() == this)) 
				throw new IllegalArgumentException("BoundingBox.subtract, " + 
				"this is not BoundingBox child's parent");
			else 
			{
				// Now that we know the child is this object's child, we continue
				// and check the subtractFrom param
				int len = SUBTRACTS.length;
				boolean valid = false;
				for (int i = 0; i < len; i++)
					if (subtractFrom == SUBTRACTS[i])
						valid = true;
				if (!valid)
					throw new IllegalArgumentException("BoundingBox.subtract, " + 
						"subtractFrom invalid: " + subtractFrom);
				// Now we know the child is valid, and if the subtractFrom 
				// preference was invalid, we subtract from the bottom

				// The child should no longer be used, since the parent 
				// reference will be invalid
				child.setParent(null);

				int cx = (int)child.getLocation().getX();
				int cy = (int)child.getLocation().getY();
				int cw = (int)child.getSize().getWidth();
				int ch = (int)child.getSize().getHeight();
				int px = (int)this.getLocation().getX();
				int py = (int)this.getLocation().getY();
				int pw = (int)this.getSize().getWidth();
				int ph = (int)this.getSize().getHeight();

				switch (subtractFrom) 
				{
				case SUBTRACT_FROM_LEFT:
					// This will be useful for right-justified Strings in tables
					pw = cx;
					this.setSize(new Dimension(pw, ph));
					return this;

				case SUBTRACT_FROM_RIGHT:
					// This will be useful for left justified Strings in tables
					px = px + cw + cx;
					pw = pw - cw - cx;
					this.setLocation(new Point(px, py));
					this.setSize(new Dimension(pw, ph));
					return this;

				case SUBTRACT_FROM_BOTTOM:
					py = py + ch + cy;
					ph = ph - ch - cy;
					this.setLocation(new Point(px, py));
					this.setSize(new Dimension(pw, ph));
					return this;

				case SUBTRACT_FROM_TOP:
					ph = cy;
					this.setSize(new Dimension(pw, ph));
					return this;

				default: // Should never happen
				break;
				} // end switch
			}
		}
		return this;
	} // end subtract

	/**
	 * <p>Gets the drawing point to use in Graphics drawing 
	 * methods.  After getting a new BoundingBox with getStringBounds(),
	 * calling this method will give you an absolute point, accounting 
	 * for alignment and padding, etc, from which to start drawing the 
	 * String</p>
	 *
	 * <p>If getStringBounds was not called (this is a parent box), the 
	 * upper left coordinates will be returned (this.getLocation())</p>
	 *
	 * @return a <code>Point</code>
	 */
	public Point getDrawingPoint() 
	{return drawingPoint;}

	// main method is for testing /////////////////

	/**
	 * For testing
	 *
	 * @param args a <code>String[]</code> value
	 */
	public static void main(String[] args) 
	{
		Point upperLeft = new Point(5, 5);
		Dimension bounds = new Dimension(100, 100);
		BoundingBox parent = new BoundingBox(upperLeft, bounds);
		String string = "Hello World!";
		Font font = new Font("SansSerif", Font.PLAIN, 12);
		Frame frame = new Frame();
		frame.addNotify();
		try 
		{
			Image image = frame.createImage(100, 100);
			if (image == null)
				System.err.println("image is null");
			Graphics graphics = image.getGraphics();
			FontMetrics fm = graphics.getFontMetrics(font);
			BoundingBox child = parent.getStringBounds(string,
					BoundingBox.HORIZ_ALIGN_LEFT,BoundingBox.VERT_ALIGN_TOP,
					fm, 5);
			System.out.println("Drawing Point: " + 
					child.getDrawingPoint().toString());
			System.out.println("Now testing subtract() method...");

			parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
			System.out.println("parent: " + parent.toString());
			child = new BoundingBox(new Point(90, 110), new Dimension(100, 100));
			parent.add(child);
			System.out.println("child: " + child.toString());
			System.out.println();
			System.out.println("subtracting the child from the parent");
			System.out.println("SUBTRACT_FROM_TOP: ");
			parent = parent.subtract(child, SUBTRACT_FROM_TOP);
			System.out.println("new parent: " + parent.toString());
			System.out.println();
			System.out.println("Resetting parent");
			parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
			parent.add(child);
			System.out.println("SUBTRACT_FROM_BOTTOM");
			parent.subtract(child, SUBTRACT_FROM_BOTTOM);
			System.out.println("new parent: " + parent.toString());
			System.out.println();
			System.out.println("Resetting parent");
			parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
			parent.add(child);
			System.out.println("SUBTRACT_FROM_LEFT");
			parent.subtract(child, SUBTRACT_FROM_LEFT);
			System.out.println("new parent: " + parent.toString());
			System.out.println();
			System.out.println("Resetting parent");
			parent = new BoundingBox(new Point(10, 10), new Dimension(300, 300));
			parent.add(child);
			System.out.println("SUBTRACT_FROM_RIGHT");
			parent.subtract(child, SUBTRACT_FROM_RIGHT);
			System.out.println("new parent: " + parent.toString());
			System.out.println();
			System.exit(0);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.exit(1);
		}
	}
	// Private methods /////////////////////////////

	/**
	 * Creates a new <code>BoundingBox</code> instance.
	 *
	 * @param p a <code>Point</code> value
	 * @param d a <code>Dimension</code> value
	 * @param drawingPoint a <code>Point</code> value
	 */
	private BoundingBox(Point p, Dimension d, Point drawingPoint,Point absolute) 
	{
		super(p, d);
		this.drawingPoint = drawingPoint;
		this.absoluteLocation = absolute;
	}

	/**
	 * <p>Checks the horizontal alignment passed into a 
	 * method to make sure it is one of the valid values</p>
	 *
	 * @param hAlign an <code>int</code> value
	 * @return a <code>boolean</code> value
	 */
	private boolean checkHAlign(int hAlign) 
	{
		int len = HORIZ_ALIGNS.length;
		for (int i = 0; i < len; i++)
			if (hAlign == HORIZ_ALIGNS[i])
				return true;
		return false;
	}

	/**
	 * <p>Checks the vertical alignment passed into a 
	 * method to make sure it is one of the valid values</p>
	 *
	 * @param vAlign an <code>int</code> value
	 * @return a <code>boolean</code> value
	 */
	private boolean checkVAlign(int vAlign)
	{
		int len = VERT_ALIGNS.length;
		for (int i = 0; i < len; i++)
			if (vAlign == VERT_ALIGNS[i])
				return true;
		return false;
	}
} // end class BoundingBox